#include "org_phnq_babel_Processor.h"
#include "Phnqlets.h"
#include "JSTypeBridge.h"
#include "ObjectFactory.h"
#include <iostream>
// #include <sstream>
// #include <boost/iostreams/stream.hpp>
// #include <boost/iostreams/concepts.hpp>  // sink

using namespace std;
using namespace phnq::phnqlets;

#define BABEL_SINK_BUF_SIZE 1024

class BabelObjectFactory : public ObjectFactory
{
	public:
		BabelObjectFactory(JNIEnv *env)
		{
			this->env = env;
		}
		
		UTHObject* createObject(UTHArray *args)
		{
			jclass cls = env->FindClass("org/phnq/babel/ObjectFactory");
			jmethodID mid = env->GetStaticMethodID(cls, "createObject", "(Lorg/phnq/babel/types/JSArray;)Lorg/phnq/babel/types/JSValue;");
			jobject arr = uth_to_j_val(this->env, args);
			jobject obj = env->CallStaticObjectMethod(cls, mid, arr);
			return (UTHObject*)j_to_uth_val(this->env, obj);
		}
		
	private:
		JNIEnv *env;
};

// class BabelSink : public boost::iostreams::sink
// {
// 	public:
// 		BabelSink(JNIEnv *env, jobject _out) : boost::iostreams::sink()
// 		{
// 			this->env = env;
// 			this->_out = _out;
// 			this->write_mid = env->GetMethodID(env->GetObjectClass(_out), "write", "([BII)V");
// 			this->buf = env->NewByteArray(BABEL_SINK_BUF_SIZE);
// 		}
// 		
// 		~BabelSink()
// 		{
// 		}
// 		
// 	    streamsize write(const char* s, streamsize n)
// 		{
// 			streamsize pos = 0;
// 			streamsize len;
// 			streamsize togo;
// 			while(pos < n)
// 			{
// 				togo = n - pos;
// 				len = togo > BABEL_SINK_BUF_SIZE ? BABEL_SINK_BUF_SIZE : togo;
// 				env->SetByteArrayRegion(buf, 0, (jint)len, (jbyte*)(s+pos));
// 				env->CallVoidMethod(_out, write_mid, buf, 0, (jint)len);
// 				pos += len;
// 			}
// 			return n;
// 		}
// 
// 	private:
// 		JNIEnv *env;
// 		jobject _out;
// 		jbyteArray buf;
// 		jmethodID write_mid;
// };

JNIEXPORT jint JNICALL Java_org_phnq_babel_Processor_initNative
  (JNIEnv *env, jobject _this)
{
	return (jint)new Processor();
}

map<string,string> convertJMap(JNIEnv *env, jobject _jmap)
{
	jmethodID entrySetMid = env->GetMethodID(env->GetObjectClass(_jmap), "entrySet", "()Ljava/util/Set;");
	jobject _entries = env->CallObjectMethod(_jmap, entrySetMid);
	
	jmethodID toArrayMid = env->GetMethodID(env->GetObjectClass(_entries), "toArray", "()[Ljava/lang/Object;");
	jobjectArray _entriesArray = (jobjectArray)env->CallObjectMethod(_entries, toArrayMid);
	
	jclass entryCls = env->FindClass("java/util/Map$Entry");
	jmethodID getKeyMid = env->GetMethodID(entryCls, "getKey", "()Ljava/lang/Object;");
	jmethodID getValueMid = env->GetMethodID(entryCls, "getValue", "()Ljava/lang/Object;");
	
	map<string,string> cmap;
	jsize mapLen = env->GetArrayLength(_entriesArray);
	for(jsize i=0; i<mapLen; i++)
	{
		jobject _entry = env->GetObjectArrayElement(_entriesArray, i);
		jstring _key = (jstring)env->CallObjectMethod(_entry, getKeyMid);
		jstring _value = (jstring)env->CallObjectMethod(_entry, getValueMid);
		
		const char *key = env->GetStringUTFChars(_key, 0);
		const char *value = env->GetStringUTFChars(_value, 0);
		
		string k(key);
		string v(value);
		cmap[k] = v;
		
		env->ReleaseStringUTFChars(_key, key);
		env->ReleaseStringUTFChars(_value, value);
	}
	
	return cmap;
}

JNIEXPORT void JNICALL Java_org_phnq_babel_Processor_destroyNative
  (JNIEnv *env, jobject _this, jint _nativeRef)
{
	Processor *processor = (Processor*)_nativeRef;
	delete processor;
}

JNIEXPORT jobject JNICALL Java_org_phnq_babel_Processor_processNative
  (JNIEnv *env, jobject _this, jint _nativeRef, jstring _path, jobject _params)
{
	const char *path = env->GetStringUTFChars(_path, 0);

	Processor *processor = (Processor*)_nativeRef;
	
	BabelObjectFactory objFactory(env);
	ObjectFactory::setCurrent(&objFactory);

	map<string,string> params = convertJMap(env, _params);
	
	string pathStr(path);
	processor_response *response = processor->process(pathStr, params);
	
	ObjectFactory::removeCurrent();
	
	jclass excCls = NULL;
	switch(response->status)
	{
		case STATUS_NOT_FOUND:
			excCls = env->FindClass("org/phnq/babel/ResourceNotFoundException");
			break;
	
		case STATUS_BAD_REQUEST:
			excCls = env->FindClass("org/phnq/babel/BadRequestException");
			break;
	
		case STATUS_FORBIDDEN:
			excCls = env->FindClass("org/phnq/babel/OperationForbiddenException");
			break;
	
		case STATUS_INTERNAL_ERROR:
			excCls = env->FindClass("org/phnq/babel/OperationErrorException");
			break;
	}

	env->ReleaseStringUTFChars(_path, path);

	if(excCls)
	{
		env->ThrowNew(excCls, "yo");
		env->DeleteLocalRef(excCls);
	}
	
	jclass procRespCls = env->FindClass("org/phnq/babel/Processor$ProcessorResponse");
	jmethodID procRespConst = env->GetMethodID(procRespCls, "<init>", "(ZJI)V");
	return env->NewObject(procRespCls, procRespConst, (jboolean)response->deflate, (jlong)response->clientCacheDuration, (jint)response->bodyStream);
}
